﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.DirectoryServices;
using System.Globalization;
using System.IO;
using System.Linq;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;
using PS.WSSDeploy.Contract;
using PS.WSSDeploy.Service.Tools;
using VastAbyss;

namespace PS.WSSDeploy.Service
{
    [Serializable]
    public class WCFService : IWSSDeployContract
    {
        private readonly string tempDir = Path.GetDirectoryName(Path.GetTempFileName());

        private static Collection<SPWebApplication> GetWebApplications()
        {
            var coll = new Collection<SPWebApplication>();

            var farm = SPFarm.Local;
            foreach (var webApp in
                farm.Services.OfType<SPWebService>().Select(service => service as SPWebService).SelectMany(webService => webService.WebApplications))
            {
                coll.Add(webApp);
            }
            return coll;
        }

        private static void ExecuteAdminJobs(string wspName)
        {
            const uint lcid = 1033;
            string jobname = "solution-deployment-" +
                ((wspName.Length >= 50) ? wspName.Substring(0, 50) : wspName) +
                "-" + lcid.ToString(NumberFormatInfo.InvariantInfo);

            var job =
                SPFarm.Local.TimerService.JobDefinitions.GetValue<SPAdministrationServiceJobDefinition>(jobname);

            if (job != null)
            {
                job.Execute(job.Service.Id);
            }
        }

        public DeployParams DeployParams { get; set; }
        public WSSFile WssFile { get; set; }
        public string WspName { get; set; }
        public string SandboxSiteUrl { get; set; }

        private void RunInOtherDomain(CrossAppDomainDelegate operation)
        {
            var otherDomain = AppDomain.CreateDomain("WcfServiceSecondDomain");
            if (otherDomain == null)
                throw new ArgumentException("Can not create domain.");
            //Utils.Trace("Create Domain");
            otherDomain.DomainUnload += AppDomainOnUnload;
            otherDomain.DoCallBack(operation);
            AppDomain.Unload(otherDomain);
        }

        public void CreateOperation()
        {
            if(WssFile == null)
                throw new ArgumentException("WssFile");

            Utils.Trace(string.Format("CreateOperation, WssFile: {0}, Domain: {1}", WssFile.Name, AppDomain.CurrentDomain.FriendlyName));

#if SP2010
            if (!string.IsNullOrEmpty(SandboxSiteUrl))
            {
                var sandboxSolutionsManager = new SandboxSolutionsManager(SandboxSiteUrl);
                sandboxSolutionsManager.Add(WssFile);
                return;
            }
#endif
            string wspName = WssFile.Name;
            byte[] wspContent = WssFile.Content;
            string wspPath = Path.Combine(tempDir, wspName);

            File.WriteAllBytes(wspPath, wspContent);
            try
            {
                SPFarm.Local.Solutions.Add(wspPath);
            }
            finally
            {
                File.Delete(wspPath);
            }
        }

        public void DeployOperation()
        {
            if (string.IsNullOrEmpty(WspName))
                throw new ArgumentException("WspName");

            if (DeployParams == null)
                throw new ArgumentException("DeployParams");

            Utils.Trace(string.Format("DeployOperation, WspName: {0}, Domain: {1}", WspName, AppDomain.CurrentDomain.FriendlyName));

#if SP2010
            var sandboxSiteUrl = DeployParams.SandboxSiteUrl;
            if (!string.IsNullOrEmpty(sandboxSiteUrl))
            {
                var sandboxSolutionsManager = new SandboxSolutionsManager(sandboxSiteUrl);
                sandboxSolutionsManager.Activate(WspName);
                return;
            }
#endif
            SPSolution solution = SPFarm.Local.Solutions[WspName];
            if (solution == null)
                throw new Win32Exception(string.Format("'{0}' does not exist in the solution store.", WspName));

            if (DeployParams.WspContainsResources)
            {
                var webApps = GetWebApplications();
                solution.DeployLocal(true, webApps, true);
            }
            else
            {
                solution.DeployLocal(true, true);
            }

            ExecuteAdminJobs(WspName);
        }

        public void UninstallOperation()
        {
            if (string.IsNullOrEmpty(WspName))
                throw new ArgumentException("WspName");

            if (DeployParams == null)
                throw new ArgumentException("DeployParams");

            //Utils.Trace(string.Format("UninstallOperation, WspName: {0}, Domain: {1}", WspName, AppDomain.CurrentDomain.FriendlyName));

#if SP2010
            var sandboxSiteUrl = DeployParams.SandboxSiteUrl;
            if (!string.IsNullOrEmpty(sandboxSiteUrl))
            {
                var sandboxSolutionsManager = new SandboxSolutionsManager(sandboxSiteUrl);
                sandboxSolutionsManager.Deactivate(WspName);
                sandboxSolutionsManager.Remove(WspName);
                return;
            }
#endif
            SPSolution solution = SPFarm.Local.Solutions[WspName];
            if (solution == null)
                throw new Win32Exception(string.Format("'{0}' does not exist in the solution store.", WspName));

            if (DeployParams.WspContainsResources)
            {
                var webApps = GetWebApplications();
                solution.RetractLocal(webApps);
            }
            else
            {
                solution.RetractLocal();
            }

            solution.Delete();
            ExecuteAdminJobs(WspName);
        }

        public void UpgradeOperation()
        {
            if (WssFile == null)
                throw new ArgumentException("WssFile");

            //Utils.Trace(string.Format("UpgradeOperation, WssFile: {0}, Domain: {1}", WssFile.Name, AppDomain.CurrentDomain.FriendlyName));
#if SP2010
            if (!string.IsNullOrEmpty(SandboxSiteUrl))
            {
                var sandboxSolutionsManager = new SandboxSolutionsManager(SandboxSiteUrl);
                sandboxSolutionsManager.Deactivate(WssFile.Name);
                sandboxSolutionsManager.Remove(WssFile.Name);
                sandboxSolutionsManager.Add(WssFile);
                sandboxSolutionsManager.Activate(WssFile.Name);
                return;
            }
#endif
            string wspName = WssFile.Name;
            byte[] wspContent = WssFile.Content;
            string wspPath = Path.Combine(tempDir, wspName);

            SPSolution solution = SPFarm.Local.Solutions[wspName];

            if (solution == null)
                throw new Win32Exception(string.Format("'{0}' does not exist in the solution store.", wspName));

            if (File.Exists(wspPath))
                File.Delete(wspPath);

            File.WriteAllBytes(wspPath, wspContent);

            solution.Upgrade(wspPath);

            //ExecuteAdminJobs(wspName);

            File.Delete(wspPath);
        }

        private void AppDomainOnUnload(object sender, EventArgs e)
        {
            //Utils.Trace("Unload Domain");
        }

        public void Create(string sandboxSiteUrl, WSSFile wsp)
        {
            // Add Solution operation may run in a same application domain safely
            SandboxSiteUrl = sandboxSiteUrl;
            WssFile = wsp;
            CreateOperation();
        }

        public void Deploy(string wspName, DeployParams prms)
        {
            var service = new WCFService {WspName = wspName, DeployParams = prms};
            RunInOtherDomain(service.DeployOperation);
        }

        public void Uninstall(string wspName, DeployParams prms)
        {
            var service = new WCFService {WspName = wspName, DeployParams = prms };
            RunInOtherDomain(service.UninstallOperation);
        }
        
        public void Upgrade(string sandboxSiteUrl, WSSFile wsp)
        {
            var service = new WCFService {WssFile = wsp, SandboxSiteUrl = sandboxSiteUrl};
            RunInOtherDomain(service.UpgradeOperation);
        }

        public void CopyToRoot(WSSFiles files)
        {
            foreach (WSSFile entry in files.Files)
            {
                string fileName = entry.Name;
                byte[] fileContent = entry.Content;
                string filePath = SPUtility.GetGenericSetupPath(fileName);

                Directory.CreateDirectory(Path.GetDirectoryName(filePath));

                File.WriteAllBytes(filePath, fileContent);
            }
        }

        public void CopyToGAC(WSSFiles files)
        {
            var installer = new AssemblyInstaller();
            installer.Install(files.Files);
        }

        public void StartRemoteDebuggerMonitor(string loginName, string password)
        {
            int slashIdx = loginName.IndexOf("\\");
            string domain = slashIdx != -1 ? loginName.Substring(0, slashIdx) : string.Empty;
            string name = slashIdx != -1 ? loginName.Substring(slashIdx + 1) : loginName;

            RunAs.StartProcess(
                name,
                domain,
                password,
                Config.RemoteDebuggerDir + "msvsmon.exe");
        }

        public void KillRemoteDebuggerMonitor()
        {
            var ps = Process.GetProcessesByName("msvsmon");

            foreach (var p in ps)
            {
                p.Kill();
            }
        }

        public void CreateDebugAccount(string loginName, string password)
        {
            throw new System.NotImplementedException();
        }

        public string GetFarmServerList()
        {
            List<string> list = new List<string>();
            SPServerCollection servers = SPFarm.Local.Servers;
            foreach (var server in servers)
            {
                if (server.Role == SPServerRole.Application ||
                    server.Role == SPServerRole.SingleServer)
                {
                    list.Add(server.Name);
                }
            }
            return string.Join("#;", list.ToArray());
        }

        public string GetWebApplicationList()
        {
            var list = new List<string>();
            var webApps = GetWebApplications();
            //foreach (var webApp in webApps.Where(webApp => !webApp.IsAdministrationWebApplication))
            foreach (var webApp in webApps)
            {
                list.Add(webApp.DisplayName);
                list.Add(webApp.AlternateUrls[0].Uri.ToString());
            }
            return string.Join("#;", list.ToArray());
        }

        public bool IsDeployed(string sandboxSiteUrl, string wspName)
        {
            bool ret;
#if SP2010
            if (!string.IsNullOrEmpty(sandboxSiteUrl))
            {
                var sandboxSolutionsManager = new SandboxSolutionsManager(sandboxSiteUrl);
                ret = sandboxSolutionsManager.IsDeployed(wspName);
                return ret;
            }
#endif
            SPSolution solution = SPFarm.Local.Solutions[wspName];
            if (solution == null)
            {
                throw new Win32Exception(
                    string.Format("'{0}' does not exist in the solution store.", wspName));
            }
            ret = solution.Deployed;
            return ret;
        }

        public bool IsExists(string sandboxSiteUrl, string wspName)
        {
            Utils.Trace(string.Format("IsExists, sandboxSiteUrl: {0}, wspName: {1}", sandboxSiteUrl, wspName));

            bool ret;
#if SP2010
            if (!string.IsNullOrEmpty(sandboxSiteUrl))
            {
                var sandboxSolutionsManager = new SandboxSolutionsManager(sandboxSiteUrl);
                ret = sandboxSolutionsManager.IsExists(wspName);
                return ret;
            }
#endif
            SPSolution solution = SPFarm.Local.Solutions[wspName];
            ret = solution != null;
            return ret;
        }

        public void CopyTo(string path, WSSFiles files)
        {
            Directory.CreateDirectory(path);

            foreach (WSSFile entry in files.Files)
            {
                string fileName = entry.Name;
                byte[] fileContent = entry.Content;
                string filePath = Path.Combine(path, fileName);
                File.WriteAllBytes(filePath, fileContent);
            }
        }

        private static IEnumerable<DirectoryEntry> GetAppPoolEntries()
        {
            var de = new DirectoryEntry("IIS://localhost/W3SVC/AppPools");
            return (from DirectoryEntry appPoolEntry in de.Children select appPoolEntry).ToList();
        }

        public void RecycleAppPools()
        {
            GetAppPoolEntries().ToList().ForEach(e =>
            {
                try
                {
                    e.Invoke("Recycle", null);
                }
                catch
                {
                }
            });
        }
    }
}
